package com.kuangstudy.framework.core;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.kuangstudy.framework.anno.GetMapping;
import com.kuangstudy.framework.anno.PostMapping;
import com.kuangstudy.framework.anno.ResponseBody;
import com.kuangstudy.framework.engine.FreemakerEngine;
import com.kuangstudy.framework.reflect.ClazzUtils;
import com.kuangstudy.framework.view.ModelAndView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {

    // 日记管理和处理
    private final Logger logger = LoggerFactory.getLogger(getClass());
    // 存放Get请求的路由
    private Map<String, GetDispatcher> getMappings = new ConcurrentHashMap<>();
    // 存放Post请求的路由
    private Map<String, PostDispatcher> postMappings = new ConcurrentHashMap<>();
    // 指定package并自动扫描:
    private List<Class<?>> controllers = new LinkedList<>();
    // 确定模板引擎
    private FreemakerEngine viewEngine;
    // 需要注入到请求方法中的参数
    private static final Set<Class<?>> supportedGetParameterTypes = Sets.newHashSet(int.class,
            long.class, boolean.class, float.class, double.class, Map.class,
            String.class, HttpServletRequest.class, HttpServletResponse.class, HttpSession.class);
    private static final Set<Class<?>> supportedPostParameterTypes = Sets.newHashSet(HttpServletRequest.class,
            HttpServletResponse.class, HttpSession.class);

    /**
     * 当Servlet容器创建当前Servlet实例后，
     * 会自动调用init(ServletConfig)方法
     */
    @Override
    public void init() throws ServletException {
        // 1: 加载controller配置包
        initController();
        // 2: 处理映射请求
        initMapping();
        // 3: 确定视图引擎
        initModelViewEngine();
    }

    public void initController() {
        Set<Class<?>> classes = ClazzUtils.getClasses("com.kuangstudy.controller");
        controllers = classes.stream().collect(Collectors.toList());
        logger.info("controllers {}...", controllers);
        logger.info("init {}...", getClass().getSimpleName());
    }

    public void initMapping() {
        // 2: 数据格式处理器
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 3：依次处理每个Controller:
        for (Class<?> controllerClass : controllers) {
            try {
                Object controllerInstance = controllerClass.getConstructor().newInstance();
                // 依次处理每个Method:
                for (Method method : controllerClass.getMethods()) {
                    if (method.getAnnotation(GetMapping.class) != null) {
                        // 处理@Get:
                        //if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                        if (method.getReturnType() == void.class) {
                            throw new UnsupportedOperationException(
                                    "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                        }
                        for (Class<?> parameterClass : method.getParameterTypes()) {
                            if (!supportedGetParameterTypes.contains(parameterClass)) {
                                throw new UnsupportedOperationException(
                                        "Unsupported parameter type: " + parameterClass + " for method: " + method);
                            }
                        }
                        // 对方法的参数进行处理
                        String[] parameterNames = Arrays.stream(method.getParameters()).map(p -> p.getName()).toArray(String[]::new);
                        // 获取注解上的路径
                        String path = method.getAnnotation(GetMapping.class).value();
                        logger.info("Found GET: {} => {}", path, method);
                        // 将路由参数统一放入到路由映射中
                        this.getMappings.put(path, new GetDispatcher(controllerInstance, method, parameterNames, method.getParameterTypes()));
                    } else if (method.getAnnotation(PostMapping.class) != null) {
                        // 处理@Post:
                        if (method.getReturnType() != ModelAndView.class && method.getReturnType() != void.class) {
                            throw new UnsupportedOperationException(
                                    "Unsupported return type: " + method.getReturnType() + " for method: " + method);
                        }
                        Class<?> requestBodyClass = null;
                        for (Class<?> parameterClass : method.getParameterTypes()) {
                            if (!supportedPostParameterTypes.contains(parameterClass)) {
                                if (requestBodyClass == null) {
                                    requestBodyClass = parameterClass;
                                } else {
                                    throw new UnsupportedOperationException("Unsupported duplicate request body type: "
                                            + parameterClass + " for method: " + method);
                                }
                            }
                        }
                        String path = method.getAnnotation(PostMapping.class).value();
                        logger.info("Found POST: {} => {}", path, method);
                        this.postMappings.put(path, new PostDispatcher(controllerInstance, method,
                                method.getParameterTypes(), objectMapper));
                    }
                }
            } catch (ReflectiveOperationException e) {
                logger.error("出现解析和加载错误....");
                e.printStackTrace();
            }
        }
    }


    public void initModelViewEngine() {
        // 创建ViewEngine:
        //this.viewEngine = new ViewEngine(getServletContext());
        this.viewEngine = new FreemakerEngine(getServletContext());
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        process(req, resp, this.getMappings);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        process(req, resp, this.postMappings);
    }

    // 统一请求处理
    private void process(HttpServletRequest req, HttpServletResponse resp, Map<String, ? extends AbstractDispatcher> dispatcherMap) throws ServletException, IOException {
        // 1: 设置请求编码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        // 2: 截取请求路径
        String path = req.getRequestURI().substring(req.getContextPath().length());
        // 3: 根据路径找到具体的类
        AbstractDispatcher dispatcher = dispatcherMap.get(path);
        // 4：如果找不到就返回
        if (dispatcher == null) {
            resp.sendError(404);
            return;
        }
        // 5: 确定模板视图
        Object mv = null;
        try {
            // 开始进行参数的注入和处理
            mv = dispatcher.invoke(req, resp);
        } catch (ReflectiveOperationException e) {
            throw new ServletException(e);
        }
        // 如果处理失败，直接返回
        if (mv == null) {
            return;
        }


        // 如果是转发，可以获取输出流
        try (PrintWriter pw = resp.getWriter();) {
            // 如果是增加了@ResponseBody注解就直接用流输出返回
            Method method = dispatcher.method;
            ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
            if (responseBody != null) {
                // json返回
                Class returnclass = mv.getClass();
                if (returnclass == int.class) {
                    pw.print(mv);
                } else if (returnclass == long.class) {
                    pw.print(mv);
                } else if (returnclass == float.class) {
                    pw.print(mv);
                } else if (returnclass == boolean.class) {
                    pw.print(mv);
                } else if (returnclass == double.class) {
                    pw.print(mv);
                } else if (returnclass == String.class) {
                    pw.print(mv);
                } else {
                    ObjectMapper objectMapper = new ObjectMapper();
                    pw.print(objectMapper.writeValueAsString(mv));
                }
            } else {
                ModelAndView modelAndView = (ModelAndView) mv;
                // 6:进入到响应环节
                // 如果有重定向，直接走重定向的环节
                if (modelAndView.view.startsWith("redirect:")) {
                    resp.sendRedirect(modelAndView.view.substring(9));
                    return;
                }
                // 7：模板的渲染转发
                this.viewEngine.render(modelAndView, pw);

                // 同步数据到作用域中
                Map<String, Object> model = modelAndView.model;
                model.entrySet().forEach(obj -> {
                     req.setAttribute(obj.getKey(),obj.getValue());
                });
            }
            // 8: 关闭渲染
            pw.flush();
        } catch (Exception ex) {

        }
    }
}